// report.go - functions related to the board's post reporting feature

package main

import (
    "./query"
    "./userstat"
    "bytes"
    "config"
    "errors"
    "html/template"
    "log"
    "net/http"
    "net/smtp"
    "strconv"
    "strings"
    txt "text/template"
    "time"
)

var condemned map[int64]*time.Timer
var text *txt.Template

type reportedPost struct {
    IntId  int64
    PostId int64
    Parent int64
}

type reportData struct {
    OffendingPosts []*reportedPost
    IntIds         string
    Addr           string
    Bname          string
}

// Confirmation page for sending reports.
func reportLanding(w http.ResponseWriter, r *http.Request,
    tmpl *template.Template, board *config.Board) {

    if !throttleReporting(w, tmpl, r.RemoteAddr) {
        return
    }
    r.ParseForm()
    var offendingPosts []*reportedPost
    var e error
    var intIds []string
    if len(r.Form) < 1 {
        errorPage(w, tmpl, "No posts selected to report",
            "No posts selected to report")
        return
    }

    for k, v := range r.Form {
        if len(v) < 1 {
            continue
        }
        iippi := strings.Split(k, "|=|")
        if len(iippi) < 3 {
            continue
        }
        p := new(reportedPost)
        p.IntId, e = strconv.ParseInt(iippi[0], 10, 64)
        p.Parent, e = strconv.ParseInt(iippi[1], 10, 64)
        p.PostId, e = strconv.ParseInt(iippi[2], 10, 64)
        if e != nil {
            errorPage(w, tmpl, "Invalid report parameter",
                "Invalid report parameter")
            return
        }
        offendingPosts = append(offendingPosts, p)
        intIds = append(intIds, iippi[0])
    }

    tmpl.ExecuteTemplate(w, "report_landing", reportData{
        OffendingPosts: offendingPosts,
        IntIds:         strings.Join(intIds, ";"),
        Bname:          board.Abbrev,
    })
}

// Actual backend work executed when confirmed. Validate input, insert report
// into reports table and associate the post and any attached image, set 
// timer to auto-purge the post and image, and send notification mail to the 
// administrator.
func reportCommit(w http.ResponseWriter, r *http.Request,
    tmpl *template.Template, board *config.Board) {

    if !throttleReporting(w, tmpl, r.RemoteAddr) {
        return
    }
    r.ParseForm()
    if len(r.Form) < 1 {
        errorPage(w, tmpl, "No posts selected to report",
            "No posts selected to report")
        return
    }

    validPosts, e := readPostNumbers(r.Form.Get("offending_posts"))
    if e != nil {
        errorPage(w, tmpl, "Invalid post", e.Error())
        return
    }
    ipMinusPort := strings.Split(r.RemoteAddr, ":")[0]
    delTime := time.Now().Add(board.AutoPurgeTime)
    for _, post := range validPosts {
        _, e := query.Get(board.TableName, "reportPost").
            Query(post, ipMinusPort, delTime)
        if e != nil {
            errorPage(w, tmpl, "Failed to update DB.",
                "Failed to update DB.")
            log.Println("ERROR: Failed to update DB for reported posts:" +
                e.Error())
            return
        }
        destroy := func() {
            log.Printf("Timer expired, purging %d", post)
            purgeAll(board, int64(post))
        }
        condemned[post] = time.AfterFunc(board.AutoPurgeTime, destroy)
    }
    go mailNotify(board, validPosts, ipMinusPort, delTime)
}

// Timed auto-delete function run by default for reported posts.
func purgeAll(board *config.Board, postId int64) {
    tbName := board.TableName
    if _, e := query.Get(tbName, "blockAssocImg").Exec(postId); e != nil {
        log.Printf("ERROR: could not block image associated "+
            "with int_id %d: %s", postId, e.Error())
    }
    if _, e := query.Get(tbName, "nullPost").Exec(postId); e != nil {
        log.Println("ERROR: could not wipe post associated "+
            "with int_id %d: %s", postId, e.Error())
    }
}

// Check that reported post numbers are valid input and return as integers.
func readPostNumbers(intString string) ([]int64, error) {
    var validPosts []int64
    for _, pStr := range strings.Split(intString, ";") {
        pInt, e := strconv.ParseInt(pStr, 10, 64)
        if e != nil {
            return nil, errors.New("Internal Post# " + pStr + " does not exist.")
        }
        validPosts = append(validPosts, pInt)
    }
    return validPosts, nil
}

// Fill out mail template and send to admin.
func mailNotify(board *config.Board, validPosts []int64,
    ip string, delTime time.Time) {
    addresses := strings.Split(board.NotifyAddr, ",")
    if len(addresses) < 1 {
        return
    }

    msg := new(bytes.Buffer)
    type values struct {
        IP      string
        Posts   []int64
        DelTime time.Time
    }
    log.Println(strings.Split(board.SMTPServer, ":")[0])
    text.ExecuteTemplate(msg, "post_reported", &values{ip, validPosts, delTime})
    auth := smtp.PlainAuth("", board.NotifyAddr, board.NotifyPass,
        strings.Split(board.SMTPServer, ":")[0])
    e := smtp.SendMail(board.SMTPServer, auth,
        board.FromEmail, addresses, msg.Bytes())
    if e != nil {
        log.Println("ERROR: Failed to send notification email: " + e.Error())
    }
}

func throttleReporting(w http.ResponseWriter,
    tmpl *template.Template, addr string) bool {

    reportsMade := userstat.FetchInfo(addr).ReportsMade
    if reportsMade > config.Defaults.ReportMax {
        errorPage(w, tmpl, "You have exceeded the allowable number of reports",
            "You have exceeded the allowable number of reports")
        return false
    }
    return true
}

func initReportSystem() {
    condemned = map[int64]*time.Timer{}
    text = txt.New("report")
    _, e := text.ParseGlob(config.Defaults.TemplatePath + "*.txt")
    if e != nil {
        panic("Unable to parse text templates.")
    }
}
